Refactored templates, controllers, and styles for improved book manag…#7
Refactored templates, controllers, and styles for improved book manag…#7JohanHiths wants to merge 2 commits intomainfrom
Conversation
…ement flow - Replaced JTE templates with Thymeleaf equivalents. - Updated `BookController` methods and added support for CRUD operations. - Introduced new Thymeleaf templates for creating and updating books. - Added global exception handling for runtime exceptions. - Enhanced styling with separate CSS files for forms and navigation. - Modified `application.properties` to enable hidden method filtering for DELETE/PUT requests.
📝 WalkthroughWalkthroughAdds a global ControllerAdvice for exception handling, introduces User model and controller, refactors BookController/Service to use pagination and domain-specific BookNotFoundException, updates templates (Thymeleaf), CSS, and application property for hidden method support. Changes
Sequence Diagram(s)sequenceDiagram
participant Client as Client
participant Controller as BookController
participant Service as BookService
participant Repo as BookRepository
participant View as Thymeleaf
participant ErrorHandler as GlobalExceptionHandler
Client->>Controller: GET /books?page=&size=
Controller->>Service: getAllBooksPaginated(page,size)
Service->>Repo: findAll(PageRequest)
Repo-->>Service: Page<Book>
Service-->>Controller: Page<Book>
Controller->>View: render "books" (books, currentPage, totalPages)
View-->>Client: HTML
alt Book not found (e.g., GET /update/{id} -> missing)
Controller->>Service: getBookById(id)
Service->>Repo: findById(id)
Repo-->>Service: empty
Service-->>Controller: throw BookNotFoundException
Controller-->>ErrorHandler: BookNotFoundException
ErrorHandler->>View: render "error" (errorMessage)
View-->>Client: 404 error page
end
alt Validation error (create/update)
Client->>Controller: POST/PUT with invalid payload
Controller->>ErrorHandler: MethodArgumentNotValidException
ErrorHandler->>View: render "error" (errors map)
View-->>Client: 400 error page
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Tip Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs). Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 11
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/main/java/org/example/lab1springboot/controller/BookController.java (1)
57-79:⚠️ Potential issue | 🔴 CriticalEdit/delete routes do not match the URLs generated by the templates.
The templates build
/books/update/{id}and/books/{id}, but these handlers are registered as/update/{id}and/{id}because the controller has no/booksbase mapping. As-is, edit and delete actions will 404.One consistent routing shape
import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.*; - `@Controller` +@RequestMapping("/books") public class BookController { @@ - `@GetMapping`("/books") + `@GetMapping` public String getBooks(`@RequestParam`(required = false) String title,Model model) { @@ - `@PostMapping`("books/create") + `@PostMapping`("/create") public String createBook(`@Valid` CreateBookDTO dto, BindingResult result) { @@ - `@GetMapping`("/update/{id}") + `@GetMapping`("/update/{id}") public String showUpdateForm(`@PathVariable` Long id, Model model) { @@ - `@GetMapping`("books/create") + `@GetMapping`("/create") public String showCreateForm(Model model) {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/org/example/lab1springboot/controller/BookController.java` around lines 57 - 79, The controller routes for edit/delete are missing the "/books" prefix; update the BookController routing so the template URLs (/books/update/{id} and /books/{id}) match the handlers: add a class-level `@RequestMapping`("/books") annotation to the BookController class (so methods updateBook, deleteBook, and showUpdateForm keep their current `@PutMapping/`@DeleteMapping/@GetMapping paths) or alternatively prefix each method mapping with "/books" to ensure consistent routing.
🧹 Nitpick comments (2)
src/main/java/org/example/lab1springboot/book/CreateBookDTO.java (1)
8-15:@Datais redundant here and widens the generated API.
@Getterand@Setteralready cover the accessors on this DTO. Adding@Dataduplicates those methods and also generatesequals/hashCode/toString, which is a bigger behavior change than this class appears to need.Smaller change
-import lombok.Data; import lombok.Getter; import lombok.Setter; `@Getter` `@Setter` -@Data public class CreateBookDTO {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/org/example/lab1springboot/book/CreateBookDTO.java` around lines 8 - 15, The class CreateBookDTO uses both `@Getter/`@Setter and `@Data` which is redundant and expands the API (adding equals/hashCode/toString); remove the `@Data` annotation and its import (lombok.Data) and keep only `@Getter` and `@Setter` on CreateBookDTO so the DTO only generates accessors and not the additional methods.src/main/java/org/example/lab1springboot/controller/BookController.java (1)
37-42: Either apply thetitlefilter or remove the parameter.The handler accepts
title, but the implementation always returnsbookService.getAllBooks(). Any caller using that query parameter will silently get unfiltered results.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/org/example/lab1springboot/controller/BookController.java` around lines 37 - 42, The getBooks handler in BookController currently accepts a title param but always returns bookService.getAllBooks(), so either apply the title filter or remove the param: if you want filtering, modify BookController.getBooks to call the service filtering method (e.g., bookService.findByTitle(title) or bookService.getBooksFilteredByTitle(title)) when title is non-empty and put that result into model.addAttribute("books", ...); if your service lacks a filter method add one (e.g., in BookService implement findByTitle or filter the list returned by getAllBooks()), and ensure null/empty title falls back to getAllBooks(); alternatively, remove the `@RequestParam`(required = false) String title from the getBooks signature and any callers expecting that query param.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/main/java/org/example/lab1springboot/controller/BookController.java`:
- Around line 75-79: The showUpdateForm method in BookController currently adds
only "book" to the model but the template expects "updateBookDTO" and "id";
change showUpdateForm (which calls bookService.getBookById) to map the retrieved
Book into an UpdateBookDTO instance, add model.addAttribute("updateBookDTO",
updateDto) and model.addAttribute("id", id) (or model.addAttribute("id",
book.getId())), so the template can bind to ${updateBookDTO} and ${id}; use the
UpdateBookDTO constructor or setters to populate fields from Book before
returning "update-book".
In `@src/main/java/org/example/lab1springboot/controller/UserController.java`:
- Around line 12-15: The CreateUser(`@RequestBody` `@Valid` User user) handler in
UserController returns the view name "user" which doesn't exist in the templates
and will fail view resolution; fix by either creating a new user.html template
matching that view name or changing the handler to return an existing view
(e.g., "home") or a redirect (e.g., "redirect:/home") after successful
creation—update the method in UserController (CreateUser) accordingly so the
returned view name matches an actual template or uses redirect to an existing
route.
- Around line 4-13: The controller is importing Tomcat's
org.apache.catalina.User instead of your application POJO, causing
deserialization to fail; update the import in UserController to use your
application's model (org.example.lab1springboot.user.User) so the `@RequestBody`
`@Valid` User user parameter refers to a concrete class, then ensure the
CreateUser method and class-level imports no longer reference
org.apache.catalina.User.
In `@src/main/java/org/example/lab1springboot/GlobalExceptionHandler.java`:
- Around line 19-24: The handler GlobalExceptionHandler.handleRuntimeException
returns the view name "error" and adds the "errorMessage" model attribute, but
there is no corresponding error template; create a new "error" template in the
templates set that displays the errorMessage (and optionally reuses navbar or
layout fragments) so that returning "error" resolves to a user-facing page
instead of causing view-resolution failure.
- Around line 19-24: The handler method in GlobalExceptionHandler —
handleRuntimeException(RuntimeException, Model) — returns an error view but
doesn't set an HTTP status; add the
`@ResponseStatus`(HttpStatus.INTERNAL_SERVER_ERROR) annotation to that method (and
import ResponseStatus and HttpStatus) so the response returns 500 instead of 200
when a RuntimeException is handled.
In `@src/main/java/org/example/lab1springboot/user/User.java`:
- Around line 26-27: The User model currently requires a client-supplied id via
the `@NotNull` annotation on the id field which prevents normal server-side ID
generation for POST /user; remove the `@NotNull` on the id field in class User (or
better: introduce a CreateUser DTO without an id and validate that instead) and
ensure ID is assigned server-side in the controller/service that handles POST
/user (refer to User.id, the `@NotNull` annotation, and the POST /user flow when
making the change).
- Around line 13-21: The validation annotations on User fields allow
blank/whitespace values; replace `@NotNull/`@NotEmpty with `@NotBlank` on the name,
username, and email fields (i.e., the private String name, private String
username, private String email in class User) to reject empty or whitespace-only
input, and add `@Email` to the email field to enforce proper email format; keep
the message attributes (or adjust them) when updating the annotations so
validation messages remain informative.
In `@src/main/resources/templates/create-book.html`:
- Around line 10-18: The create-book template is missing the form bound to the
CreateBookDTO expected by BookController.createBook; add an HTML <form> in
create-book.html that uses Thymeleaf binding (th:object="${book}"), includes
inputs bound to the DTO properties (e.g., th:field for title, author, isbn,
etc.), and posts to the controller endpoint (the action that
BookController.createBook handles) with a submit button so the controller
receives a populated CreateBookDTO.
In `@src/main/resources/templates/home.html`:
- Around line 13-14: The template uses ${message} and ${currentDate} but
HomeController.home() only adds activePage, so those bindings will be empty; fix
by either (A) adding model attributes in HomeController.home(): put
model.addAttribute("message", "<desired text>") and
model.addAttribute("currentDate", LocalDate.now() or formatted date) so the
th:text bindings have backing data, or (B) revert the template to static content
by replacing <h1 th:text="${message}"> and <span th:text="${currentDate}"> with
plain text values; update the HomeController.home() method or the <h1>/<span>
elements accordingly.
In `@src/main/resources/templates/navbar.html`:
- Around line 6-10: The navbar stylesheet link is outside the exported fragment,
so pages that replace the node with the header fragment (th:fragment="navbar")
lose the CSS; move the <link th:href="@{/css/navbar.css}" rel="stylesheet"> into
the fragment (inside the header element defined by th:fragment="navbar") or
alternatively change the fragment to include a head fragment that injects the
stylesheet so that consumers of the header fragment (navbar) automatically get
the /css/navbar.css without needing to add it manually; update the template
around th:fragment="navbar" and the existing <link th:href...> accordingly.
In `@src/main/resources/templates/update-book.html`:
- Around line 14-17: The form in update-book.html currently has the title/author
input fields commented out so the Update Book button submits no editable data;
restore the inputs by uncommenting or re-adding the input elements that bind to
the model (the lines using th:field="*{title}" and th:field="*{author}") so the
form includes the title and author values on submit, ensuring they remain inside
the same <form> as the <button type="submit"> and keep any required attributes
(e.g., id/name or th:field) consistent with the backing model.
---
Outside diff comments:
In `@src/main/java/org/example/lab1springboot/controller/BookController.java`:
- Around line 57-79: The controller routes for edit/delete are missing the
"/books" prefix; update the BookController routing so the template URLs
(/books/update/{id} and /books/{id}) match the handlers: add a class-level
`@RequestMapping`("/books") annotation to the BookController class (so methods
updateBook, deleteBook, and showUpdateForm keep their current
`@PutMapping/`@DeleteMapping/@GetMapping paths) or alternatively prefix each
method mapping with "/books" to ensure consistent routing.
---
Nitpick comments:
In `@src/main/java/org/example/lab1springboot/book/CreateBookDTO.java`:
- Around line 8-15: The class CreateBookDTO uses both `@Getter/`@Setter and `@Data`
which is redundant and expands the API (adding equals/hashCode/toString); remove
the `@Data` annotation and its import (lombok.Data) and keep only `@Getter` and
`@Setter` on CreateBookDTO so the DTO only generates accessors and not the
additional methods.
In `@src/main/java/org/example/lab1springboot/controller/BookController.java`:
- Around line 37-42: The getBooks handler in BookController currently accepts a
title param but always returns bookService.getAllBooks(), so either apply the
title filter or remove the param: if you want filtering, modify
BookController.getBooks to call the service filtering method (e.g.,
bookService.findByTitle(title) or bookService.getBooksFilteredByTitle(title))
when title is non-empty and put that result into model.addAttribute("books",
...); if your service lacks a filter method add one (e.g., in BookService
implement findByTitle or filter the list returned by getAllBooks()), and ensure
null/empty title falls back to getAllBooks(); alternatively, remove the
`@RequestParam`(required = false) String title from the getBooks signature and any
callers expecting that query param.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: eec187c8-4a73-49cf-9b77-7ccfc09b4ecc
📒 Files selected for processing (21)
src/main/java/org/example/lab1springboot/GlobalExceptionHandler.javasrc/main/java/org/example/lab1springboot/book/BookService.javasrc/main/java/org/example/lab1springboot/book/CreateBookDTO.javasrc/main/java/org/example/lab1springboot/controller/BookController.javasrc/main/java/org/example/lab1springboot/controller/HomeController.javasrc/main/java/org/example/lab1springboot/controller/UserController.javasrc/main/java/org/example/lab1springboot/user/User.javasrc/main/jte/books.jtesrc/main/jte/home.jtesrc/main/jte/updatebook.jtesrc/main/resources/application.propertiessrc/main/resources/static/css/base.csssrc/main/resources/static/css/books.csssrc/main/resources/static/css/forms.csssrc/main/resources/static/css/navbar.csssrc/main/resources/static/style.csssrc/main/resources/templates/books.htmlsrc/main/resources/templates/create-book.htmlsrc/main/resources/templates/home.htmlsrc/main/resources/templates/navbar.htmlsrc/main/resources/templates/update-book.html
💤 Files with no reviewable changes (2)
- src/main/jte/home.jte
- src/main/jte/books.jte
| @GetMapping("/update/{id}") | ||
| public String showUpdateForm(@PathVariable Long id, Model model) { | ||
| Book book = bookService.getBookById(id); | ||
| model.addAttribute("book", book); | ||
| return "update-book"; |
There was a problem hiding this comment.
showUpdateForm populates the wrong model attributes for the template.
update-book.html binds to ${updateBookDTO} and uses ${id}, but this method only exposes "book". The edit page will fail to bind correctly on the initial GET render.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/java/org/example/lab1springboot/controller/BookController.java`
around lines 75 - 79, The showUpdateForm method in BookController currently adds
only "book" to the model but the template expects "updateBookDTO" and "id";
change showUpdateForm (which calls bookService.getBookById) to map the retrieved
Book into an UpdateBookDTO instance, add model.addAttribute("updateBookDTO",
updateDto) and model.addAttribute("id", id) (or model.addAttribute("id",
book.getId())), so the template can bind to ${updateBookDTO} and ${id}; use the
UpdateBookDTO constructor or setters to populate fields from Book before
returning "update-book".
src/main/java/org/example/lab1springboot/controller/UserController.java
Outdated
Show resolved
Hide resolved
| @PostMapping("/user") | ||
| public String CreateUser(@RequestBody @Valid User user) { | ||
|
|
||
| return "user"; |
There was a problem hiding this comment.
Return an existing view or add user.html.
The success path returns "user", but the provided templates only include books.html, create-book.html, home.html, navbar.html, and update-book.html. As written, a successful POST /user still fails during view resolution.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/java/org/example/lab1springboot/controller/UserController.java`
around lines 12 - 15, The CreateUser(`@RequestBody` `@Valid` User user) handler in
UserController returns the view name "user" which doesn't exist in the templates
and will fail view resolution; fix by either creating a new user.html template
matching that view name or changing the handler to return an existing view
(e.g., "home") or a redirect (e.g., "redirect:/home") after successful
creation—update the method in UserController (CreateUser) accordingly so the
returned view name matches an actual template or uses redirect to an existing
route.
| @ExceptionHandler(RuntimeException.class) | ||
| public String handleRuntimeException(RuntimeException ex, Model model) { | ||
|
|
||
| model.addAttribute("errorMessage", ex.getMessage()); | ||
|
|
||
| return "error"; |
There was a problem hiding this comment.
Add the missing error template or this handler will fail on first use.
This method returns "error", but the provided template set only includes books.html, create-book.html, home.html, navbar.html, and update-book.html. Without src/main/resources/templates/error.html, any handled RuntimeException turns into a view-resolution failure instead of a user-facing error page.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/java/org/example/lab1springboot/GlobalExceptionHandler.java` around
lines 19 - 24, The handler GlobalExceptionHandler.handleRuntimeException returns
the view name "error" and adds the "errorMessage" model attribute, but there is
no corresponding error template; create a new "error" template in the templates
set that displays the errorMessage (and optionally reuses navbar or layout
fragments) so that returning "error" resolves to a user-facing page instead of
causing view-resolution failure.
🧩 Analysis chain
🌐 Web query:
In Spring MVC, when an @ExceptionHandlermethod returns a String view name and does not use@ResponseStatus, ResponseEntity, or otherwise set the response status, what HTTP status is sent?
💡 Result:
It will be HTTP 200 (OK) by default (i.e., if your @ExceptionHandler returns a String view name and you don’t set a status via @ResponseStatus, ResponseEntity, HttpServletResponse, etc., Spring leaves the response status unchanged, and the servlet container’s default is 200). [1], [2]
🏁 Script executed:
find . -name "GlobalExceptionHandler.java" -type fRepository: JohanHiths/Lab1-Springboot
Length of output: 140
🏁 Script executed:
cat -n "./src/main/java/org/example/lab1springboot/GlobalExceptionHandler.java"Repository: JohanHiths/Lab1-Springboot
Length of output: 1073
Add @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) to the exception handler.
When an @ExceptionHandler method returns a view name without explicitly setting the response status, Spring defaults to HTTP 200 (OK). This causes error responses to be treated as successful responses by clients and monitoring systems, breaking HTTP semantics. The handler needs the @ResponseStatus annotation to return the correct status code.
Suggested fix
+import org.springframework.http.HttpStatus;
import org.springframework.ui.Model;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.servlet.View;
@@
`@ExceptionHandler`(RuntimeException.class)
+ `@ResponseStatus`(HttpStatus.INTERNAL_SERVER_ERROR)
public String handleRuntimeException(RuntimeException ex, Model model) {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| @ExceptionHandler(RuntimeException.class) | |
| public String handleRuntimeException(RuntimeException ex, Model model) { | |
| model.addAttribute("errorMessage", ex.getMessage()); | |
| return "error"; | |
| `@ExceptionHandler`(RuntimeException.class) | |
| `@ResponseStatus`(HttpStatus.INTERNAL_SERVER_ERROR) | |
| public String handleRuntimeException(RuntimeException ex, Model model) { | |
| model.addAttribute("errorMessage", ex.getMessage()); | |
| return "error"; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/java/org/example/lab1springboot/GlobalExceptionHandler.java` around
lines 19 - 24, The handler method in GlobalExceptionHandler —
handleRuntimeException(RuntimeException, Model) — returns an error view but
doesn't set an HTTP status; add the
`@ResponseStatus`(HttpStatus.INTERNAL_SERVER_ERROR) annotation to that method (and
import ResponseStatus and HttpStatus) so the response returns 500 instead of 200
when a RuntimeException is handled.
| @NotNull(message = "Name cannot be empty") | ||
| private String name; | ||
|
|
||
| @NotEmpty | ||
| @NotNull(message = "Username cannot be empty") | ||
| private String username; | ||
|
|
||
| @NotNull(message = "Email cannot be empty") | ||
| private String email; |
There was a problem hiding this comment.
These constraints still allow blank user data.
@NotNull accepts "", and @NotEmpty on username still allows whitespace-only values. If this object is used for request validation, blank name, username, and email inputs will pass despite the current messages. Use @NotBlank, and add @Email for email.
Suggested fix
+import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
-import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
@@
- `@NotNull`(message = "Name cannot be empty")
+ `@NotBlank`(message = "Name cannot be empty")
private String name;
@@
- `@NotEmpty`
- `@NotNull`(message = "Username cannot be empty")
+ `@NotBlank`(message = "Username cannot be empty")
private String username;
@@
- `@NotNull`(message = "Email cannot be empty")
+ `@NotBlank`(message = "Email cannot be empty")
+ `@Email`(message = "Email is invalid")
private String email;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| @NotNull(message = "Name cannot be empty") | |
| private String name; | |
| @NotEmpty | |
| @NotNull(message = "Username cannot be empty") | |
| private String username; | |
| @NotNull(message = "Email cannot be empty") | |
| private String email; | |
| `@NotBlank`(message = "Name cannot be empty") | |
| private String name; | |
| `@NotBlank`(message = "Username cannot be empty") | |
| private String username; | |
| `@NotBlank`(message = "Email cannot be empty") | |
| `@Email`(message = "Email is invalid") | |
| private String email; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/java/org/example/lab1springboot/user/User.java` around lines 13 -
21, The validation annotations on User fields allow blank/whitespace values;
replace `@NotNull/`@NotEmpty with `@NotBlank` on the name, username, and email
fields (i.e., the private String name, private String username, private String
email in class User) to reject empty or whitespace-only input, and add `@Email` to
the email field to enforce proper email format; keep the message attributes (or
adjust them) when updating the annotations so validation messages remain
informative.
| @NotNull(message = "Id cannot be empty") | ||
| private String id; |
There was a problem hiding this comment.
Don’t require a client-supplied id on the create model.
This type is validated on POST /user, so @NotNull here forces callers to invent an identifier before creation. That usually breaks create flows or lets clients pick IDs that should be server-controlled.
Suggested direction
- `@NotNull`(message = "Id cannot be empty")
private String id;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| @NotNull(message = "Id cannot be empty") | |
| private String id; | |
| private String id; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/java/org/example/lab1springboot/user/User.java` around lines 26 -
27, The User model currently requires a client-supplied id via the `@NotNull`
annotation on the id field which prevents normal server-side ID generation for
POST /user; remove the `@NotNull` on the id field in class User (or better:
introduce a CreateUser DTO without an id and validate that instead) and ensure
ID is assigned server-side in the controller/service that handles POST /user
(refer to User.id, the `@NotNull` annotation, and the POST /user flow when making
the change).
| <header> <img class="logo" alt="Bookstore Logo" width="200"> | ||
|
|
||
|
|
||
| <div th:replace="~{navbar :: navbar}"></div> | ||
|
|
||
|
|
||
| </header> | ||
|
|
||
| </body> |
There was a problem hiding this comment.
Create page cannot submit a book.
BookController.createBook(...) expects a bound CreateBookDTO, but this template renders only header/navigation markup. Without a form bound to ${book}, users cannot create anything.
Minimal fix sketch
<header> <img class="logo" alt="Bookstore Logo" width="200">
-
-
<div th:replace="~{navbar :: navbar}"></div>
-
-
</header>
+
+<main>
+ <form th:action="@{/books/create}" method="post" th:object="${book}">
+ <input type="text" th:field="*{title}" placeholder="Title">
+ <input type="text" th:field="*{author}" placeholder="Author">
+ <button type="submit">Create Book</button>
+ </form>
+</main>
</body>
</html>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <header> <img class="logo" alt="Bookstore Logo" width="200"> | |
| <div th:replace="~{navbar :: navbar}"></div> | |
| </header> | |
| </body> | |
| <header> <img class="logo" alt="Bookstore Logo" width="200"> | |
| <div th:replace="~{navbar :: navbar}"></div> | |
| </header> | |
| <main> | |
| <form th:action="@{/books/create}" method="post" th:object="${book}"> | |
| <input type="text" th:field="*{title}" placeholder="Title"> | |
| <input type="text" th:field="*{author}" placeholder="Author"> | |
| <button type="submit">Create Book</button> | |
| </form> | |
| </main> | |
| </body> |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/resources/templates/create-book.html` around lines 10 - 18, The
create-book template is missing the form bound to the CreateBookDTO expected by
BookController.createBook; add an HTML <form> in create-book.html that uses
Thymeleaf binding (th:object="${book}"), includes inputs bound to the DTO
properties (e.g., th:field for title, author, isbn, etc.), and posts to the
controller endpoint (the action that BookController.createBook handles) with a
submit button so the controller receives a populated CreateBookDTO.
| <h1 th:text="${message}">Welcome Message</h1> | ||
| <p>Today's date is: <span th:text="${currentDate}">Date</span></p> |
There was a problem hiding this comment.
These bindings no longer have backing model attributes.
HomeController.home() only adds activePage, so ${message} and ${currentDate} will render empty here. Either repopulate them in the controller or switch this section back to static content.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/resources/templates/home.html` around lines 13 - 14, The template
uses ${message} and ${currentDate} but HomeController.home() only adds
activePage, so those bindings will be empty; fix by either (A) adding model
attributes in HomeController.home(): put model.addAttribute("message", "<desired
text>") and model.addAttribute("currentDate", LocalDate.now() or formatted date)
so the th:text bindings have backing data, or (B) revert the template to static
content by replacing <h1 th:text="${message}"> and <span
th:text="${currentDate}"> with plain text values; update the
HomeController.home() method or the <h1>/<span> elements accordingly.
| <link th:href="@{/css/navbar.css}" rel="stylesheet"> | ||
| </head> | ||
| <body> | ||
|
|
||
| <header class="navbar" th:fragment="navbar"> |
There was a problem hiding this comment.
Navbar CSS is not carried with the fragment.
Consumers replace a node with the header fragment from Line 10, but the stylesheet link at Line 6 stays in this standalone template. Pages that include ~{navbar :: navbar} will therefore render an unstyled navbar unless they also import /css/navbar.css themselves.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/resources/templates/navbar.html` around lines 6 - 10, The navbar
stylesheet link is outside the exported fragment, so pages that replace the node
with the header fragment (th:fragment="navbar") lose the CSS; move the <link
th:href="@{/css/navbar.css}" rel="stylesheet"> into the fragment (inside the
header element defined by th:fragment="navbar") or alternatively change the
fragment to include a head fragment that injects the stylesheet so that
consumers of the header fragment (navbar) automatically get the /css/navbar.css
without needing to add it manually; update the template around
th:fragment="navbar" and the existing <link th:href...> accordingly.
| <!-- <input type="text" th:field="*{title}"--> | ||
| <!-- <input type="text" th:field="*{author}"--> | ||
|
|
||
| <button type="submit">Update Book</button> |
There was a problem hiding this comment.
The update form submits no book fields.
With the inputs commented out, clicking Update Book sends no editable data, so the request cannot change the title/author values.
Minimal fix sketch
-<!-- <input type="text" th:field="*{title}"-->
-<!-- <input type="text" th:field="*{author}"-->
+ <input type="text" th:field="*{title}">
+ <input type="text" th:field="*{author}">
<button type="submit">Update Book</button>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <!-- <input type="text" th:field="*{title}"--> | |
| <!-- <input type="text" th:field="*{author}"--> | |
| <button type="submit">Update Book</button> | |
| <input type="text" th:field="*{title}"> | |
| <input type="text" th:field="*{author}"> | |
| <button type="submit">Update Book</button> |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/resources/templates/update-book.html` around lines 14 - 17, The form
in update-book.html currently has the title/author input fields commented out so
the Update Book button submits no editable data; restore the inputs by
uncommenting or re-adding the input elements that bind to the model (the lines
using th:field="*{title}" and th:field="*{author}") so the form includes the
title and author values on submit, ensuring they remain inside the same <form>
as the <button type="submit"> and keep any required attributes (e.g., id/name or
th:field) consistent with the backing model.
- Added pagination support to `BookController` and updated templates with pagination controls. - Introduced `BookNotFoundException` for improved error handling. - Enhanced `GlobalExceptionHandler` to handle various exceptions including validation and runtime errors. - Updated styles and templates to improve user experience for error and paging scenarios. - Refactored `BookService` for cleaner logic and added transactional annotations.
There was a problem hiding this comment.
Actionable comments posted: 5
♻️ Duplicate comments (1)
src/main/java/org/example/lab1springboot/controller/BookController.java (1)
77-83:⚠️ Potential issue | 🟠 MajorPopulate the DTO before rendering the edit form.
The book is loaded on Line 77, but
dtostays empty. The initial render will lose the current title/author values.Suggested fix
public String showUpdateForm(`@PathVariable` Long id, Model model) { Book book = bookService.getBookById(id); UpdateBookDTO dto = new UpdateBookDTO(); + dto.setTitle(book.getTitle()); + dto.setName(book.getAuthor()); model.addAttribute("updateBookDTO", dto); model.addAttribute("id", id); return "update-book"; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/org/example/lab1springboot/controller/BookController.java` around lines 77 - 83, The UpdateBookDTO is created empty after loading the book in BookController (bookService.getBookById(id)) so the edit form shows no current values; populate the DTO fields from the loaded Book (e.g., title, author, etc.) before calling model.addAttribute("updateBookDTO", dto) and keep adding the id as before so the form is pre-filled with the book's existing data.
🧹 Nitpick comments (1)
src/main/java/org/example/lab1springboot/controller/BookController.java (1)
94-99: Remove the duplicateBookNotFoundExceptionhandler.
src/main/java/org/example/lab1springboot/GlobalExceptionHandler.java:26-33already returns the same 404/error view. Keeping both copies makes future changes easy to drift.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/org/example/lab1springboot/controller/BookController.java` around lines 94 - 99, Remove the duplicate BookNotFoundException handler method from BookController: delete the handleBookNotFound(BookNotFoundException ex, Model model) method so the application uses the centralized GlobalExceptionHandler (which already handles BookNotFoundException and returns the 404/error view). After removal, clean up any now-unused imports in BookController and run tests to ensure no references remain.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/main/java/org/example/lab1springboot/controller/BookController.java`:
- Around line 57-58: The route annotations in BookController (e.g., updateBook)
don't match the URLs used by the templates (/books/update/{id} and posts to
/books/{id}), causing 404s; update the mapping annotations so they are prefixed
with /books (for example change `@PutMapping`("/{id}") to
`@PutMapping`("/books/{id}") and any `@GetMapping` for the edit form to
`@GetMapping`("/books/update/{id}")), and likewise adjust the delete handler
mapping (e.g., deleteBook) to `@PostMapping` or `@DeleteMapping`("/books/{id}") as
the template expects; update the annotations on the methods named updateBook,
deleteBook (and the edit form handler, if present) so the controller paths and
templates are consistent.
- Around line 101-107: Validate the paging parameters in the controller before
calling bookService.getAllBooksPaginated: in getAllBooks check that page >= 0
and size >= 1 and if not throw a client error (e.g., throw new
ResponseStatusException(HttpStatus.BAD_REQUEST, "page must be >= 0 and size must
be >= 1")) so a bad query string yields HTTP 400 instead of allowing
PageRequest.of(page, size) (or any similar call inside
bookService.getAllBooksPaginated) to throw a 500.
In `@src/main/resources/templates/error.html`:
- Line 7: The template contains a malformed HTML tag '<<body>' which breaks
markup; open the template and replace the incorrect '<<body>' token with a
proper '<body>' start tag and ensure there is a matching '</body>' closing tag
(verify surrounding template logic around the body element) so the document
structure is valid.
- Around line 10-11: The template hardcodes "404" causing wrong status display;
change the <h1> to render a status placeholder (e.g., use the template engine's
expression like ${status}) instead of the literal "404", and ensure
GlobalExceptionHandler populates that model attribute when returning the error
view (update the methods in GlobalExceptionHandler to add a "status" attribute
with the actual HTTP status code before returning the template).
- Around line 14-16: The template only displays errorMessage and thus ignores
validation details placed by
GlobalExceptionHandler.handleMethodArgumentNotValidException under the "errors"
model attribute; update the error.html to also render the "errors" collection
(e.g., check if errors exists and iterate to show each field/message) in
addition to the existing errorMessage so validation failures surface the actual
field-level messages.
---
Duplicate comments:
In `@src/main/java/org/example/lab1springboot/controller/BookController.java`:
- Around line 77-83: The UpdateBookDTO is created empty after loading the book
in BookController (bookService.getBookById(id)) so the edit form shows no
current values; populate the DTO fields from the loaded Book (e.g., title,
author, etc.) before calling model.addAttribute("updateBookDTO", dto) and keep
adding the id as before so the form is pre-filled with the book's existing data.
---
Nitpick comments:
In `@src/main/java/org/example/lab1springboot/controller/BookController.java`:
- Around line 94-99: Remove the duplicate BookNotFoundException handler method
from BookController: delete the handleBookNotFound(BookNotFoundException ex,
Model model) method so the application uses the centralized
GlobalExceptionHandler (which already handles BookNotFoundException and returns
the 404/error view). After removal, clean up any now-unused imports in
BookController and run tests to ensure no references remain.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 2a403413-5ce7-4623-acf6-f31f4f5251be
📒 Files selected for processing (10)
pom.xmlsrc/main/java/org/example/lab1springboot/BookNotFoundException.javasrc/main/java/org/example/lab1springboot/GlobalExceptionHandler.javasrc/main/java/org/example/lab1springboot/book/BookService.javasrc/main/java/org/example/lab1springboot/controller/BookController.javasrc/main/java/org/example/lab1springboot/controller/UserController.javasrc/main/resources/application.propertiessrc/main/resources/static/css/books.csssrc/main/resources/templates/books.htmlsrc/main/resources/templates/error.html
✅ Files skipped from review due to trivial changes (2)
- src/main/resources/static/css/books.css
- pom.xml
🚧 Files skipped from review as they are similar to previous changes (1)
- src/main/java/org/example/lab1springboot/controller/UserController.java
| @PutMapping("/{id}") | ||
| public String updateBook(@PathVariable Long id, @Valid UpdateBookDTO updateBookDTO, BindingResult bindingResult, Model model) { |
There was a problem hiding this comment.
Prefix the edit and delete endpoints consistently.
src/main/resources/templates/books.html links to /books/update/{id} and posts to /books/{id}, but these handlers are registered at /update/{id} and /{id}. Edit and delete will 404 until the mappings match.
Suggested fix
- `@PutMapping`("/{id}")
+ `@PutMapping`("/books/{id}")
public String updateBook(`@PathVariable` Long id, `@Valid` UpdateBookDTO updateBookDTO, BindingResult bindingResult, Model model) {
@@
- `@DeleteMapping`("/{id}")
+ `@DeleteMapping`("/books/{id}")
public String deleteBook(`@PathVariable` Long id) {
@@
- `@GetMapping`("/update/{id}")
+ `@GetMapping`("/books/update/{id}")
public String showUpdateForm(`@PathVariable` Long id, Model model) {Also applies to: 68-75
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/java/org/example/lab1springboot/controller/BookController.java`
around lines 57 - 58, The route annotations in BookController (e.g., updateBook)
don't match the URLs used by the templates (/books/update/{id} and posts to
/books/{id}), causing 404s; update the mapping annotations so they are prefixed
with /books (for example change `@PutMapping`("/{id}") to
`@PutMapping`("/books/{id}") and any `@GetMapping` for the edit form to
`@GetMapping`("/books/update/{id}")), and likewise adjust the delete handler
mapping (e.g., deleteBook) to `@PostMapping` or `@DeleteMapping`("/books/{id}") as
the template expects; update the annotations on the methods named updateBook,
deleteBook (and the edit form handler, if present) so the controller paths and
templates are consistent.
| @GetMapping("/books") | ||
| public String getAllBooks( | ||
| @RequestParam(defaultValue = "0") int page, | ||
| @RequestParam(defaultValue = "5") int size, | ||
| Model model) { | ||
|
|
||
| Page<Book> bookPage = bookService.getAllBooksPaginated(page, size); |
There was a problem hiding this comment.
Reject invalid paging parameters up front.
PageRequest.of(page, size) will throw for page < 0 or size < 1, so a bad query string currently turns into a 500 instead of a client error.
Suggested fix
+import org.springframework.web.server.ResponseStatusException;
@@
public String getAllBooks(
`@RequestParam`(defaultValue = "0") int page,
`@RequestParam`(defaultValue = "5") int size,
Model model) {
+ if (page < 0 || size < 1) {
+ throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid pagination parameters");
+ }
Page<Book> bookPage = bookService.getAllBooksPaginated(page, size);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/java/org/example/lab1springboot/controller/BookController.java`
around lines 101 - 107, Validate the paging parameters in the controller before
calling bookService.getAllBooksPaginated: in getAllBooks check that page >= 0
and size >= 1 and if not throw a client error (e.g., throw new
ResponseStatusException(HttpStatus.BAD_REQUEST, "page must be >= 0 and size must
be >= 1")) so a bad query string yields HTTP 400 instead of allowing
PageRequest.of(page, size) (or any similar call inside
bookService.getAllBooksPaginated) to throw a 500.
| <meta charset="UTF-8"> | ||
| <title>Title</title> | ||
| </head> | ||
| <<body> |
There was a problem hiding this comment.
Fix the malformed <body> tag.
<<body> leaves the template with invalid markup and can break layout/styling unpredictably.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/resources/templates/error.html` at line 7, The template contains a
malformed HTML tag '<<body>' which breaks markup; open the template and replace
the incorrect '<<body>' token with a proper '<body>' start tag and ensure there
is a matching '</body>' closing tag (verify surrounding template logic around
the body element) so the document structure is valid.
| <h1>404</h1> | ||
| <h2>Woopsie daisy! Page went to the moon.</h2> |
There was a problem hiding this comment.
Don't hardcode 404 in this shared error page.
src/main/java/org/example/lab1springboot/GlobalExceptionHandler.java also returns this template for 400 and 500, so those paths will incorrectly render as “404”.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/resources/templates/error.html` around lines 10 - 11, The template
hardcodes "404" causing wrong status display; change the <h1> to render a status
placeholder (e.g., use the template engine's expression like ${status}) instead
of the literal "404", and ensure GlobalExceptionHandler populates that model
attribute when returning the error view (update the methods in
GlobalExceptionHandler to add a "status" attribute with the actual HTTP status
code before returning the template).
| <p th:text="${errorMessage}">We cannot find the page you were looking for</p> | ||
|
|
||
| <a th:href="@{/}" style="text-decoration: none; color: white; background: #6d15b5; padding: 10px 20px; border-radius: 5px;">Back to Earth (Home)</a> |
There was a problem hiding this comment.
Render validation errors here as well.
GlobalExceptionHandler.handleMethodArgumentNotValidException(...) stores field errors under errors, but this template only reads errorMessage. Validation failures will therefore lose the actual reason.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/resources/templates/error.html` around lines 14 - 16, The template
only displays errorMessage and thus ignores validation details placed by
GlobalExceptionHandler.handleMethodArgumentNotValidException under the "errors"
model attribute; update the error.html to also render the "errors" collection
(e.g., check if errors exists and iterate to show each field/message) in
addition to the existing errorMessage so validation failures surface the actual
field-level messages.
…ement flow
BookControllermethods and added support for CRUD operations.application.propertiesto enable hidden method filtering for DELETE/PUT requests.Summary by CodeRabbit
New Features
Improvements
Style